﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenTK;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Input;
using System.Drawing;
using OpenTK.Graphics;
using Common;
using AntTweakBar;

namespace T19_Misc05_1_picking_slow_easy {
    class T19_Misc05_1_picking_slow_easyGameWindow : GameWindow {
        public T19_Misc05_1_picking_slow_easyGameWindow() {
            this.Load += T19_Misc05_1_picking_slow_easyGameWindow_Load;
            this.Resize += T19_Misc05_1_picking_slow_easyGameWindow_Resize;
            this.RenderFrame += T19_Misc05_1_picking_slow_easyGameWindow_RenderFrame;
            this.Unload += T19_Misc05_1_picking_slow_easyGameWindow_Unload;
            this.Width = 1024;
            this.Height = 768;
            this.Title = "T19_Misc05_1_picking_slow_easyGameWindow";

        }


        private void T19_Misc05_1_picking_slow_easyGameWindow_RenderFrame(object sender, FrameEventArgs e) {
            GL.ClearColor(.1f, .1f, .4f, .1f);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            Render();
            context.Draw();
            SwapBuffers();
            ProcessEvents();
        }

        Vao vao;
        Shader shader, pickingShader;
        Texture2D texture;
        ModelMesh mesh;

        VertexAttribManager pickAttribs = new VertexAttribManager();
        VertexAttribManager attribs = new VertexAttribManager();

        private void T19_Misc05_1_picking_slow_easyGameWindow_Load(object sender, EventArgs e) {
            InitializeBars();
            VSync = VSyncMode.Off;
            GL.Enable(EnableCap.DepthTest);
            GL.DepthFunc(DepthFunction.Less);
            GL.Enable(EnableCap.CullFace);

            vao = new Vao();
            shader = new Shader("shader.vs", "shader.fs");
            pickingShader = new Shader("Picking.vertexshader", "Picking.fragmentshader");
            texture = Texture2D.Load("uvmap.dds");
            mesh = MeshLoader.LoadObjIndexed("suzanne.obj");
            mesh.CreateVbo();
            pickAttribs.AddAttrib(3, mesh.VertexBuffer);
            attribs.AddAttrib(3, mesh.VertexBuffer);
            attribs.AddAttrib(2, mesh.UvBuffer);
            attribs.AddAttrib(3, mesh.NormalBuffer);

            InitializePosOrient();

        }

        const int objectCount = 100;
        Vector3[] positions = new Vector3[objectCount];
        Quaternion[] orientations = new Quaternion[objectCount];

        int rand() {
            return random.Next();
        }
        Random random = new Random();

        private void InitializePosOrient() {
            for (int i = 0; i < positions.Length; i++) {
                positions[i] = new Vector3(rand() % 20 - 10, rand() % 20 - 10, rand() % 20 - 10);
                orientations[i] = new Quaternion(rand() % 360, rand() % 360, rand() % 360);
            }
        }

        Context context;
        StringVariable messageVar;

        private void InitializeBars() {
            context = new Context(Tw.GraphicsAPI.OpenGL);
            Bar GUI = new Bar(context) { Label = "Picking", ValueColumnWidth = 100, Size=new Size(340,200) };
            GUI.SetDefinition("refresh=0.1");
            messageVar = new StringVariable(GUI) { Label = "Last picked object", };
            messageVar.Value = "test";
        }
        protected override void OnResize(EventArgs e) {
            base.OnResize(e);
            context.HandleResize(this.ClientSize);
        }
        private void Render() {
            InputControls.LockCursor = true;
            InputControls.computeMatricesFromInput(this);
            var projection = InputControls.ProjectionMatrix;
            var view = InputControls.ViewMatrix;
            if (isLeftButtonDown()) {
                GL.ClearColor(1, 1, 1, 1);
                GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
                pickingShader.Use();
                pickAttribs.Enable();
                // Draw the 100 monkeys, each with a slighly different color
                for (int i = 0; i < positions.Length; i++) {


                    //glm::mat4 RotationMatrix = glm::toMat4(orientations[i]);
                    Matrix4 RotationMatrix = Matrix4.CreateFromQuaternion(orientations[i]);
                    //glm::mat4 TranslationMatrix = translate(mat4(), positions[i]);
                    Matrix4 TranslationMatrix = Matrix4.CreateTranslation(positions[i]);
                    //glm::mat4 ModelMatrix = TranslationMatrix * RotationMatrix;
                    Matrix4 modelMatrix = RotationMatrix * TranslationMatrix;

                    //glm::mat4 MVP = ProjectionMatrix * ViewMatrix * ModelMatrix;
                    Matrix4 mvp = modelMatrix * view * projection;

                    // Send our transformation to the currently bound shader, 
                    // in the "MVP" uniform
                    //glUniformMatrix4fv(PickingMatrixID, 1, GL_FALSE, &MVP[0][0]);
                    pickingShader.SetMatrix4("MVP", mvp);

                    // Convert "i", the integer mesh ID, into an RGB color
                    int r = (i & 0x000000FF) >> 0;
                    int g = (i & 0x0000FF00) >> 8;
                    int b = (i & 0x00FF0000) >> 16;

                    // OpenGL expects colors to be in [0,1], so divide by 255.
                    //glUniform4f(pickingColorID, r / 255.0f, g / 255.0f, b / 255.0f, 1.0f);
                    pickingShader.SetVector4("PickingColor", r / 255.0f, g / 255.0f, b / 255.0f, 1.0f);

                    //// 1rst attribute buffer : vertices
                    //glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
                    //glVertexAttribPointer(
                    //    0,                  // attribute
                    //    3,                  // size
                    //    GL_FLOAT,           // type
                    //    GL_FALSE,           // normalized?
                    //    0,                  // stride
                    //    (void*)0            // array buffer offset
                    //);

                    //// Index buffer
                    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);

                    //// Draw the triangles !
                    //glDrawElements(
                    //    GL_TRIANGLES,      // mode
                    //    indices.size(),    // count
                    //    GL_UNSIGNED_SHORT,   // type
                    //    (void*)0           // element array buffer offset
                    //);
                    //pickAttribs.Enable();
                    mesh.IndexBuffer.Bind();
                    GL.DrawElements(BeginMode.Triangles, mesh.Indices.Length, DrawElementsType.UnsignedShort, 0);
                }

                //glDisableVertexAttribArray(0);
                pickAttribs.Disable();


                // Wait until all the pending drawing commands are really done.
                // Ultra-mega-over slow ! 
                // There are usually a long time between glDrawElements() and
                // all the fragments completely rasterized.
                //glFlush();
                GL.Flush();
                //glFinish();
                GL.Finish();


                //glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
                GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);

                // Read the pixel at the center of the screen.
                // You can also use glfwGetMousePos().
                // Ultra-mega-over slow too, even for 1 pixel, 
                // because the framebuffer is on the GPU.
                //unsigned char data[4];
                byte[] data = new byte[4];
                //glReadPixels(1024 / 2, 768 / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, data);
                GL.ReadPixels(this.Width / 2, this.Height / 2, 1, 1, PixelFormat.Rgb, PixelType.UnsignedByte, data);

                // Convert the color back to an integer ID
                int pickedID =
                    data[0] +
                    data[1] * 256 +
                    data[2] * 256 * 256;

                if (pickedID == 0x00ffffff) { // Full white, must be the background !
                    messageVar.Value = "background";
                }
                else {
                    //std::ostringstream oss;
                    //oss << "mesh " << pickedID;
                    //message = oss.str();
                    messageVar.Value = $"mesh {pickedID}";
                }

                // Uncomment these lines to see the picking shader in effect
                //glfwSwapBuffers(window);
                //continue; // skips the normal rendering


            }



            //end if
            // Dark blue background
            //glClearColor(0.0f, 0.0f, 0.4f, 0.0f);
            GL.ClearColor(0, 0, .4f, 0);
            // Re-clear the screen for real rendering
            //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);


            // Use our shader
            //glUseProgram(programID);
            shader.Use();
            attribs.Enable();
            //glEnableVertexAttribArray(0);
            //glEnableVertexAttribArray(1);
            //glEnableVertexAttribArray(2);

            //for (int i = 0; i < positions.Length; i++) {
            for (int i = 0; i < positions.Length; i++) {


                //glm::mat4 RotationMatrix = glm::toMat4(orientations[i]);
                var RotationMatrix = Matrix4.CreateFromQuaternion(orientations[i]);
                //glm::mat4 TranslationMatrix = translate(mat4(), positions[i]);
                Matrix4 TranslationMatrix = Matrix4.CreateTranslation( positions[i]);
                //glm::mat4 ModelMatrix = TranslationMatrix * RotationMatrix;
                var ModelMatrix = RotationMatrix * TranslationMatrix;

                var MVP = ModelMatrix * view * projection;

                // Send our transformation to the currently bound shader, 
                // in the "MVP" uniform
                //glUniformMatrix4fv(MatrixID, 1, GL_FALSE, &MVP[0][0]);
                //glUniformMatrix4fv(ModelMatrixID, 1, GL_FALSE, &ModelMatrix[0][0]);
                //glUniformMatrix4fv(ViewMatrixID, 1, GL_FALSE, &ViewMatrix[0][0]);
                shader.SetMatrix4("MVP", MVP);
                shader.SetMatrix4("M", ModelMatrix);
                shader.SetMatrix4("V", view);

                //glm::vec3 lightPos = glm::vec3(4, 4, 4);
                //glUniform3f(LightID, lightPos.x, lightPos.y, lightPos.z);
                shader.SetVector3("LightPosition_worldspace", 4, 4, 4);

                //// Bind our texture in Texture Unit 0
                //glActiveTexture(GL_TEXTURE0);
                //glBindTexture(GL_TEXTURE_2D, Texture);
                //// Set our "myTextureSampler" sampler to user Texture Unit 0
                //glUniform1i(TextureID, 0);
                texture.Active(TextureUnit.Texture0, shader, "myTextureSampler");

                //// 1rst attribute buffer : vertices
                //glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
                //glVertexAttribPointer(
                //    0,                  // attribute
                //    3,                  // size
                //    GL_FLOAT,           // type
                //    GL_FALSE,           // normalized?
                //    0,                  // stride
                //    (void*)0            // array buffer offset
                //);

                //// 2nd attribute buffer : UVs
                //glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
                //glVertexAttribPointer(
                //    1,                                // attribute
                //    2,                                // size
                //    GL_FLOAT,                         // type
                //    GL_FALSE,                         // normalized?
                //    0,                                // stride
                //    (void*)0                          // array buffer offset
                //);

                //// 3rd attribute buffer : normals
                //glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
                //glVertexAttribPointer(
                //    2,                                // attribute
                //    3,                                // size
                //    GL_FLOAT,                         // type
                //    GL_FALSE,                         // normalized?
                //    0,                                // stride
                //    (void*)0                          // array buffer offset
                //);
                attribs.Enable();
                // Index buffer
                //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementbuffer);
                mesh.IndexBuffer.Bind();

                //// Draw the triangles !
                //glDrawElements(
                //    GL_TRIANGLES,      // mode
                //    indices.size(),    // count
                //    GL_UNSIGNED_SHORT,   // type
                //    (void*)0           // element array buffer offset
                //);
                GL.DrawElements(BeginMode.Triangles, mesh.Indices.Length, DrawElementsType.UnsignedShort, 0);

            }

            //glDisableVertexAttribArray(0);
            //glDisableVertexAttribArray(1);
            //glDisableVertexAttribArray(2);
            attribs.Disable();


        }

        private bool isLeftButtonDown() {
            return InputControls.isMouseButtonDown(MouseButton.Left);
        }

        private void T19_Misc05_1_picking_slow_easyGameWindow_Resize(object sender, EventArgs e) {
            GL.Viewport(0, 0, Width, Height);
        }



        private void T19_Misc05_1_picking_slow_easyGameWindow_Unload(object sender, EventArgs e) {
            //GlDisposableObjectBase.DisposeObjects(vao, shader,pickingShader);
            GlDisposableObjectBase.DisposeAllDisposableMembers(this);
        }

    }
}
